//
//  controller.m
//  cocoa_client
//
//  Created by ixtli on 7/30/08.
//  Copyright 2008 __MyCompanyName__. All rights reserved.
//

#import "controller.h"

@implementation controller


- (void)awakeFromNib
{
	[_logView setString:@""];
	_connected = false;
	_debug = true;
	
}

- (void)setupService
{
	if( _connected == true ) return;
	
	[self writeToLog:@"Client: Connecting...\n"];
	
	struct sockaddr_in dest_addr;
	
	[self debug:@"Client: Opening socket.\n"];
	_sockfd = socket(PF_INET, SOCK_STREAM, 0);
	if( _sockfd == -1 )
	{
		// something went wrong
		[self error:[[NSString alloc] initWithCString: strerror(errno) encoding: [NSString defaultCStringEncoding]]];
		return;
	}
	
	dest_addr.sin_family = AF_INET; // host byte order
	
	unsigned int port = (unsigned int) [[_portField stringValue] intValue];
	if( port < 1025 || port > 65534 ) port = 30000; // just deal with it
	
	dest_addr.sin_port = htons(port); // short, network byte order
	dest_addr.sin_addr.s_addr = inet_addr([[_hostField stringValue] cStringUsingEncoding: [NSString defaultCStringEncoding]]);
	
	[self debug:[NSString stringWithFormat:@"Client: Connecting to %@ on port %i\n", [_hostField stringValue], port]]; 
	
	if( connect(_sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == -1 )
	{
		// something went wrong
		[self error:[[NSString alloc] initWithCString: strerror(errno) encoding: [NSString defaultCStringEncoding]]];
		return;
	}
	
	// try authentication
	action_data msg;
	msg.action = CL_AUTHENTICATE;
	strncpy(msg.body.fields.username, [[_nameField stringValue] cStringUsingEncoding: [NSString defaultCStringEncoding]], 20 );
	strncpy(msg.body.fields.password, [[_passField stringValue] cStringUsingEncoding: [NSString defaultCStringEncoding]], 20 );
	
	[self writeToLog:[NSString stringWithFormat:@"Client: Authenticating as user %@... \n", [_nameField stringValue]]];
	send(_sockfd, (void*)&msg, sizeof(action_data), 0);
	[self debug:@"Waiting for authentication response...\n"];
	char buff[sizeof(server_response)];
	int nbytes = recv(_sockfd, &buff, sizeof(buff), 0);
	if( nbytes == 0 )
	{
		// server disconnected
		[self error:@"recv(): Lost server connection.  Aborting.\n"];
		return;
	} else if ( nbytes != sizeof(buff)) {
		[self error:@"recv(): server response incomplete.\n"];
		return;
	}
	
	server_response *resp = (server_response *) &buff;
	switch ( resp->type )
	{
		case SV_SUCCESS:
			[self writeToLog:[NSString stringWithFormat:@"Client:  Authentication successful for user %@.\n", [_nameField stringValue]]];
			_connected = true;
			break;
			
		case SV_DISCONNECT:
			[self error:@"Server disconnected.\n"];
			return;
			
		default:
			[self writeServerMessageToLog:resp->type previousAction:resp->body.fields.previous_action serverError: resp->body.fields.error];
	}
	
	// Create NSFileHandle to communicate with socket
    _listeningSocket = [[NSFileHandle alloc] initWithFileDescriptor:_sockfd];
	
	// Register for NSFileHandle socket-related Notifications
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc addObserver:self
		   selector:@selector(handleServerMessage:)
			   name:NSFileHandleDataAvailableNotification
			 object:nil];
	
	// Accept connections in background and notify
	[_listeningSocket waitForDataInBackgroundAndNotify];
	
	_userName = [[NSString alloc] initWithString:[_nameField stringValue]];
	_connected = true;
	[_connectButton setTitle:@"Disconnect"];
	
}

- (void)stopService
{
	[self debug:@"Client: Disconnecting...\n"];
	
	// TODO: disconnect NSFileHandle
	
	action_data msg;
	msg.action = CL_DISCONNECT;
	strcpy(msg.body.msg, "Gotta Go.");
	send(_sockfd, (void *)&msg, sizeof(msg), 0);
	
	// TODO: wait for SV_DISCONNECT
	
	close(_sockfd);
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[_listeningSocket closeFile];
	[_listeningSocket release];
	
	[_userName release];
	_connected = false;
	
	[_connectButton setTitle:@"Connect"];
	[self writeToLog:@"Client: Disconnected.\n"];
}

- (IBAction)sendMessage:(id)sender
{
	if( _connected == false || [[_messageField stringValue] isEqualToString:@""] )
		return;
	
	action_data msg;
	msg.action = CL_SAY;
	strncpy(msg.body.msg, [[_messageField stringValue] cStringUsingEncoding: [NSString defaultCStringEncoding]], sizeof(msg.body.msg));
	msg.body.msg[sizeof(msg.body)-1] = '\0';
	send(_sockfd, (void *)&msg, sizeof(msg), 0);
	
	[self writeToLog:[NSString stringWithFormat:@"<%@>\t%@\n", _userName, [_messageField stringValue]]];
	[_messageField setStringValue:@""];	
}

- (IBAction)toggleDebugOutput:(id)sender
{
	if ( _debug )
		_debug = false;
	else
		_debug = true;
}

- (IBAction)toggleConnect:(id)sender
{
	if( _connected == false )
		[self setupService];
	else
		[self stopService];
}

- (void)writeServerMessageToLog:(response_types)type previousAction:(client_actions)action serverError:(server_error)error
{
	NSString *message;
	
	switch (action)
	{
		case CL_AUTHENTICATE:
			message = @"Authentication: ";
			break;
			
		case CL_CREATE_ACCOUNT:
			message = @"Account Creation: ";
			break;
			
		default:
			message = @"Unknown action: ";
			break;
	}
	
	switch (type)
	{
		case SV_SUCCESS: 
			[message stringByAppendingString:@"Successfull.\n"];
			[self writeToLog:message];
			break;
			
		case SV_ERROR:
			switch (error)
			{
				case ERR_NONE:
					[message stringByAppendingString:@"Successful.\n"];
					break;
					
				case ERR_INTERNAL:
					[message stringByAppendingString:@"Server error.\n"];
					break;
					
				case ERR_CORRUPT_CLIENT_MESSAGE:
					[message stringByAppendingString:@"Our message was corruped.\n"];
					break;
					
				case ERR_USER_EXISTS:
					[message stringByAppendingString:@"User already exists.\n"];
					break;
					
				case ERR_NONEXISTANT_USER:
					[message stringByAppendingString:@"User does not exist.\n"];
					break;
					
				case ERR_EMAIL_IN_USE:
					[message stringByAppendingString:@"Email is already in use.\n"];
					break;
					
				case ERR_INCORRECT_PASSWORD:
					[message stringByAppendingString:@"Password is incorrect.\n"];
					break;
					
				default:
					[message stringByAppendingString:@"Unknown error code.\n"];
					break;
			}
			[self error:message];
			
			break;
	}
}

- (IBAction)createAccount:(id)sender
{
	
}

- (BOOL)isConnected
{
	return _connected;
}

- (void)debug:(NSString *)message
{
	if(_debug) [self writeToLog:message];
}

- (void)writeToLog:(NSString*)message
{
	NSString *str = [_logView string];
	str = [str stringByAppendingString:message];
	[_logView setString:str];
	//[self scrollToBottom];
}

- (void)error:(NSString *)message
{
	NSString *str = [_logView string];
	str = [str stringByAppendingString:@"Error: "];
	str = [str stringByAppendingString:message];
	[_logView setString:str];
	//[self scrollToBottom];
}

// from http://developer.apple.com/documentation/Cocoa/Conceptual/NSScrollViewGuide/Articles/Scrolling.html
- (void)scrollToBottom:(id)sender;
{
    NSPoint newScrollOrigin;
	
    if ([[_scrollView documentView] isFlipped]) {
        newScrollOrigin=NSMakePoint(0.0,NSMaxY([[_scrollView documentView] frame])
									-NSHeight([[_scrollView contentView] bounds]));
    } else {
        newScrollOrigin=NSMakePoint(0.0,0.0);
    }
	
    [[_scrollView documentView] scrollPoint:newScrollOrigin];
}

@end

@implementation controller (NotificationHandling)

- (void)handleServerMessage:(NSNotification *)notification
{
	char buff[sizeof(server_response)];
	int nbytes = recv(_sockfd, &buff, sizeof(buff), 0 );
	if( nbytes == 0 )
	{
		// server has disconnected.
		[self error:@"recv(): Lost connection to server.  Press return to exit.\n"];
		[self stopService];
		return;
	} else if( nbytes != sizeof(buff) ) {
		[self error:@"recv(): Incomplete server response.\n"];
		return;
	}
	
	server_response *resp = (server_response*)&buff;
	
	switch( resp->type )
	{
		case SV_MSG:
			// for safety's sake, append '\0' to the end of msg
			resp->body.msg[sizeof(resp->body.msg) - 1] = '\0';
			[self writeToLog:[NSString stringWithFormat:@"< User >\t%s\n", resp->body.msg]];
			break;
			
		case SV_DISCONNECT:
			// server has disconnected
			[self error:@"Server disconnected.\n"];
			[self stopService];
			break;
			
		default:
			[self writeServerMessageToLog:resp->type previousAction:resp->body.fields.previous_action serverError: resp->body.fields.error];
			break;
	}
	
	[_listeningSocket waitForDataInBackgroundAndNotify];
}

@end
